package com.bugsnag.ohos;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import com.bugsnag.ohos.util.OhosLog;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

public class AnrPlugin implements Plugin {
    private final String LOAD_ERR_MSG = "Native library could not be linked.";
    private final String ANR_ERROR_CLASS = "ANR";
    private final String ANR_ERROR_MSG = "Application did not respond to UI input";
    private EventRunner eventRunner = EventRunner.getMainEventRunner();
    private EventHandler eventHandler = new EventHandler(eventRunner);

    public AnrPlugin() {
    }

    protected boolean doesJavaTraceLeadToNativeTrace(StackTraceElement[] javaTrace) {
        OhosLog.d("AnrPlugin", "doesJavaTraceLeadToNativeTrace()");

        if (javaTrace == null || javaTrace.length == 0) {
            return false;
        }
        OhosLog.d("AnrPlugin", "doesJavaTraceLeadToNativeTrace() 2");
        // The only check that will work across all Ohos versions is the isNativeMethod call.
        return javaTrace[0].isNativeMethod();
    }

    private LibraryLoader libraryLoader = new LibraryLoader();
    private final AtomicBoolean oneTimeSetupPerformed = new AtomicBoolean(false);
    private Client client;
    private AnrDetailsCollector collector = new AnrDetailsCollector();

    public native void setUnwindFunction(Long unwindFunction);

    public native void enableAnrReporting();

    public native void disableAnrReporting();

    private Class loadClass(String clz) {
        try {
            return Class.forName(clz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }


    private void initNativePlugin() {
        enableAnrReporting();
        client.logger.i("Initialised ANR Plugin");
        OhosLog.d("AnrPlugin", "Initialised ANR Plugin this :%s", this);
    }

    private void performOneTimeSetup(Client client) {
        libraryLoader.loadLibrary("bugsnag_plugin_ohos_anr", client, new OnErrorCallback() {
            @Override
            public boolean onError(Event event) {
                OhosLog.e("AnrPlugin", " error.setErrorClass(\"AnrLinkError\")");
                Error error = event.getErrors().get(0);
                error.setErrorClass("AnrLinkError");
                error.setErrorMessage(LOAD_ERR_MSG);
                return true;
            }
        });
        final Class<Plugin> clz = loadClass("com.bugsnag.ohos.NdkPlugin");
        if (clz != null) {
            Plugin ndkPlugin = client.getPlugin(clz);
            if (ndkPlugin != null) {
                //TODO  JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0x730ad510e8
//                Method method = null;
//                try {
//                    method = ndkPlugin.getClass().getMethod("getUnwindStackFunction");
//                    long function = (long) method.invoke(ndkPlugin);
//                    setUnwindFunction(function);
//                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
//                    e.printStackTrace();
//                    OhosLog.e("AnrPlugin","performOneTimeSetup() error %s:",e.toString());
//                }
            }
        }
    }

    /**
     * Notifies bugsnag that an ANR has occurred, by generating an Error report and populating it
     * with details of the ANR. Intended for internal use only.
     */
    private void notifyAnrDetected(List<NativeStackframe> nativeTrace) {
        OhosLog.d("AnrPlugin", "notifyAnrDetected() AnrPlugin : %s", this);
        OhosLog.d("AnrPlugin", "notifyAnrDetected() size : %d", nativeTrace.size());
        try {

            if (client.immutableConfig.shouldDiscardError(ANR_ERROR_CLASS)) {
                return;
            }
            OhosLog.d("AnrPlugin", "notifyAnrDetected() start going");
            // generate a full report as soon as possible, then wait for extra process error info

            /*Looper.getMainLooper().thread.stackTrace;*/
            StackTraceElement[] stackTrace = java.lang.Thread.currentThread().getStackTrace();

            OhosLog.d("AnrPlugin", "notifyAnrDetected() 11");
            boolean hasNativeComponent = doesJavaTraceLeadToNativeTrace(stackTrace);
            OhosLog.d("AnrPlugin", "notifyAnrDetected() 2 hasNativeComponent =%s ", hasNativeComponent);
            Exception exc = new RuntimeException();
            OhosLog.d("AnrPlugin", "notifyAnrDetected() 3");
            exc.setStackTrace(stackTrace);
            OhosLog.d("AnrPlugin", "notifyAnrDetected() 4");
            Event event = NativeInterface.createEvent(exc, client, SeverityReason.newInstance(SeverityReason.REASON_ANR));
            OhosLog.d("AnrPlugin", "notifyAnrDetected() 5");
            Error err = event.getErrors().get(0);
            err.setErrorClass(ANR_ERROR_CLASS);
            err.setErrorMessage(ANR_ERROR_MSG);
            OhosLog.d("AnrPlugin", "notifyAnrDetected() hasNativeComponent = %s", hasNativeComponent);
            // append native stackframes to error/thread stacktrace
            if (hasNativeComponent) {
                // update error stacktrace
                List<Stackframe> stackframes = new ArrayList<>();
                for (NativeStackframe nativeFrames : nativeTrace) {
                    stackframes.add(new Stackframe(nativeFrames));
                }
                err.getStacktrace().addAll(0, stackframes);
                // update thread stacktrace
                List<Thread> threadList = event.getThreads();
                if (threadList != null && threadList.size() > 0) {
                    Thread errThread = null;
                    for (int j = 0; j < threadList.size(); j++) {
                        if (threadList.get(j).getErrorReportingThread()) {
                            errThread = threadList.get(j);
                            break;
                        }
                    }
                    if (errThread != null && errThread.getStacktrace() != null) {
                        errThread.getStacktrace().addAll(0, stackframes);
                    }
                }
            }

            // wait and poll for error info to be collected. this occurs just before the ANR dialog
            // is displayed
            collector.collectAnrErrorDetails(client, event);
        } catch (Exception exception) {
            OhosLog.e("AnrPlugin", "Internal error reporting ANR exception: %s", exception.toString());
            client.logger.e("Internal error reporting ANR", exception);
        }
    }

    @Override
    public void load(Client client) {
        this.client = client;
        OhosLog.d("AnrPlugin", "Initialised ANR Plugin this :%s", this);
        OhosLog.d("AnrPlugin", "Initialised ANR Plugin client :%s", client);
        if (!oneTimeSetupPerformed.getAndSet(true)) {
            performOneTimeSetup(client);
        }
        if (libraryLoader.isLoaded()) {
 /*           final mainLooper =Looper.getMainLooper();
            if (Looper.myLooper() == mainLooper) {
                initNativePlugin();
            } else {
                Handler(mainLooper).postAtFrontOfQueue {
                    initNativePlugin();
                }
            }*/
            eventHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    initNativePlugin();
                }
            });
        } else {
            client.logger.e(LOAD_ERR_MSG);
        }
    }

    @Override
    public void unload() {
        if (libraryLoader.isLoaded()) {
            disableAnrReporting();
        }
    }
}
